-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local cjson = require 'cjson'
local log = require 'mc.logging'
local context = require 'mc.context'
local mdb_service = require 'mc.mdb.mdb_service'

local SENSOR_TYPE_TEMPERATURE<const> = 1
local SENSOR_TYPE_FAN<const> = 4

local m = {}

local health_t = {
    ['OK'] = 'OK',
    ['Minor'] = 'Warning',
    ['Major'] = 'Warning',
    ['Critical'] = 'Critical'
}

local severity_t = {
    ['OK'] = 'Informational',
    ['Minor'] = 'Minor',
    ['Major'] = 'Major',
    ['Critical'] = 'Critical'
}

local function get_object_service(path, intf)
    local service = nil
    local ok, rsp = pcall(mdb.get_object, bus, path, intf)
    if ok then
        service = rsp.service
    end
    return service
end

local function get_object_all_properties(service, path, intf)
    local err, rsp = bus:pcall(service, path, 'bmc.kepler.Object.Properties',
        'GetAllWithContext', 'a{ss}s', context.get_context() or context.new(), intf)

    if not err then
        return true, rsp
    end
    return false, rsp
end

-- 获取进风口或者硬盘的自定义曲线数据
function m.get_temperature(custom_policy_obj)
    local res, env_temp_range
    if next(custom_policy_obj) ~= nil then
        res = cjson.json_object_new_object()
        env_temp_range = cjson.json_object_new_array()
        for idx = 2, #custom_policy_obj.TemperatureRangeLow do
            env_temp_range[#env_temp_range + 1] = custom_policy_obj.TemperatureRangeLow[idx]
        end
        res['TemperatureRangeCelsius'] = env_temp_range
        res['FanSpeedPercents'] = cjson.json_object_from_table(custom_policy_obj.SpeedRangeLow)
        res['MinTemperatureRangeCelsius'] = 0
        res['MaxTemperatureRangeCelsius'] = 60
        res['MinFanSpeedPercents'] = custom_policy_obj.FanSpeedRangePercents[1]
        res['MaxFanSpeedPercents'] = custom_policy_obj.FanSpeedRangePercents[2]
    end
    return res
end

function m.get_target(data)
    local tmp_data = cjson.json_object_to_table(data)
    local ret = {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}}
    for _, obj in ipairs(tmp_data) do
        ret[obj.TemperatureType].CustomTargetTemperatureCelsius =
            obj.TargetTemperatureCelsius == 0 and cjson.null or obj.TargetTemperatureCelsius
        ret[obj.TemperatureType].MinTargetTemperatureCelsius =
            obj.MinTargetTemperatureCelsius == 0 and cjson.null or obj.MinTargetTemperatureCelsius
        ret[obj.TemperatureType].MaxTargetTemperatureCelsius =
            obj.MaxTargetTemperatureCelsius == 0 and cjson.null or obj.MaxTargetTemperatureCelsius
    end
    return ret
end

function m.orchestrate_paths(paths)
    local ret = {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}}
    local service = nil
    local ok, rsp
    local tt
    for _, v in ipairs(paths) do
        if service == nil then
            service = get_object_service(v, 'bmc.kepler.Systems.CoolingRequirement')
        end

        if service == nil then
            goto continue
        end

        ok, rsp = get_object_all_properties(service, v, 'bmc.kepler.Systems.CoolingRequirement')
        if ok then
            tt = rsp['TemperatureType']:value()
            local range_l = string.byte(rsp['TargetTemperatureRangeCelsius']:value(), 1)
            local range_h = string.byte(rsp['TargetTemperatureRangeCelsius']:value(), 2)
            if tt >= 1 and tt <= 10 then
                ret[tt][#ret[tt] + 1] = {
                    path = v,
                    range_l = range_l,
                    range_h = range_h
                }
            end
        end
        ::continue::
    end
    return ret
end

local properties = {
    'CPUTargetTemperatureCelsius',
    'OutletTargetTemperatureCelsius',
    'DiskTargetTemperatureCelsius',
    'MemoryTargetTemperatureCelsius',
    'PCHTargetTemperatureCelsius',
    'VRDTargetTemperatureCelsius',
    'VDDQTargetTemperatureCelsius',
    'NPUHbmTargetTemperatureCelsius',
    'NPUAiCoreTargetTemperatureCelsius',
    'NPUBoardTargetTemperatureCelsius'
}

local function get_first_obj_info(paths, req_body, index)
    local property_name = properties[index]
    if req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom[property_name] and #paths[index] == 0 then
        local args = 'Oem/{{OemIdentifier}}/FanSpeedCustom/' .. property_name
        local err = custom_messages.PropertyModificationNotSupported(args)
        err.RelatedProperties = {'#/' .. args}
        error(err)
    end

    return paths[index][1].path, paths[index][1].range_l, paths[index][1].range_h
end

local function check_range(input, range_l, range_h, property)
    if input ~= nil and range_l ~= nil and range_h ~= nil then
        if input < range_l or input > range_h then
            local arg1 = input
            local arg2 = 'Oem/{{OemIdentifier}}/FanSpeedCustom/' .. property
            local err = custom_messages.PropertyValueOutOfRange(arg1, arg2)
            err.RelatedProperties = {'#/' .. arg2}
            error(err)
        end
    end
end

function m.check_CPU(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 1)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.CPUTargetTemperatureCelsius,
        range_l, range_h, 'CPUTargetTemperatureCelsius')

    return path
end

function m.check_Outlet(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 2)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.OutletTargetTemperatureCelsius,
        range_l, range_h, 'OutletTargetTemperatureCelsius')
    return path
end

function m.check_Disk(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 3)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.DiskTargetTemperatureCelsius,
        range_l, range_h, 'DiskTargetTemperatureCelsius')
    return path
end

function m.check_Memory(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 4)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.MemoryTargetTemperatureCelsius,
        range_l, range_h, 'MemoryTargetTemperatureCelsius')
    return path
end

function m.check_PCH(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 5)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.PCHTargetTemperatureCelsius,
        range_l, range_h, 'PCHTargetTemperatureCelsius')
    return path
end

function m.check_VRD(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 6)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.VRDTargetTemperatureCelsius,
        range_l, range_h, 'VRDTargetTemperatureCelsius')
    return path
end

function m.check_VDDQ(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 7)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.VDDQTargetTemperatureCelsius,
        range_l, range_h, 'VDDQTargetTemperatureCelsius')
    return path
end

function m.check_NPUHbm(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 8)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.NPUHbmTargetTemperatureCelsius,
        range_l, range_h, 'NPUHbmTargetTemperatureCelsius')
    return path
end

function m.check_NPUAiCore(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 9)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.NPUAiCoreTargetTemperatureCelsius,
        range_l, range_h, 'NPUAiCoreTargetTemperatureCelsius')
    return path
end

function m.check_NPUBoard(paths, req_body)
    local path, range_l, range_h = get_first_obj_info(paths, req_body, 10)
    check_range(req_body.Oem["{{OemIdentifier}}"].FanSpeedCustom.NPUBoardTargetTemperatureCelsius,
        range_l, range_h, 'NPUBoardTargetTemperatureCelsius')
    return path
end

function m.get_temperature_sensors_summary(temp_sensors)
    local level_count = {
        ['Minor'] = 0,
        ['Major'] = 0,
        ['Critical'] = 0
    }
    local severity
    for _, v in pairs(temp_sensors) do
        if cjson.json_object_is_object(v) then
            -- 防止拿到预期之外的severity值做nil+0操作
            level_count[v.Status.Oem["{{OemIdentifier}}"].Severity] =
                (level_count[v.Status.Oem["{{OemIdentifier}}"].Severity] or 0) + 1
        end
    end
    -- 取最严重的温度传感器状态返回
    if level_count['Critical'] > 0 then
        severity = 'Critical'
    elseif level_count['Major'] > 0 then
        severity = 'Major'
    elseif level_count['Minor'] > 0 then
        severity = 'Minor'
    else
        severity = 'OK'
    end

    local res  = cjson.json_object_new_object()
    res.Count = #temp_sensors
    res.Status = cjson.json_object_new_object()
    res.Status.Severity = severity_t[severity]
    res.Status.HealthRollup = health_t[severity]
    return res
end

function m.get_sensors_paths(datas)
    local fan_sensor_paths = {}
    local tmp_sensor_paths = {}
    for _, v in ipairs(datas) do
        if v['SensorType'] == SENSOR_TYPE_FAN then
            fan_sensor_paths[#fan_sensor_paths + 1] = '/' .. #fan_sensor_paths .. v['Path']
        end
        if v['SensorType'] == SENSOR_TYPE_TEMPERATURE then
            tmp_sensor_paths[#tmp_sensor_paths + 1] = '/' .. #tmp_sensor_paths .. v['Path']
        end
    end
    return {
        FanPaths = fan_sensor_paths,
        TmpPaths = tmp_sensor_paths
    }
end

local function get_thermal_fans(list)
    local res = {}
    for _, path in pairs(list) do
        local ok, fan_obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.Fan')
        if not ok then
            goto continue
        end
        res[fan_obj.FanId] = fan_obj

        ::continue::
    end
    return res
end

local function get_fan_obj(sensor_name, thermal_fans)
    local _, fan_index = string.match(sensor_name, '(FAN)(%d+)')
    if fan_index ~= nil then
        fan_index = tonumber(fan_index)
        return thermal_fans[fan_index]
    end
    return nil
end

local function get_fan_name(sensor_name)
    local _, fan_index, isDouble = string.match(sensor_name, '(FAN)(%d+)[ _]([FR])[ _]')
    if isDouble == 'F' then
        return string.format('Fan%s%s', fan_index, ' Front')
    elseif isDouble == 'R' then
        return string.format('Fan%s%s', fan_index, ' Rear')
    else
        return string.format('Fan%s', fan_index)
    end
end

local function get_fan_reading(sensor_name, fan_obj)
    if not fan_obj then
        return cjson.null
    end
    local name = get_fan_name(sensor_name)
    if string.find(name, 'Front') then
        if fan_obj.FrontPresence == 0 then
            return cjson.null
        else
            return fan_obj.FrontSpeed * fan_obj.Coefficient
        end
    elseif string.find(name, 'Rear') then
        if fan_obj.RearPresence == 0 then
            return cjson.null
        else
            return fan_obj.RearSpeed * fan_obj.Coefficient
        end
    end
end

local function get_fan_status(sensor_obj, fan_obj)
    local rsp = cjson.json_object_new_object()
    rsp.Health = cjson.null
    rsp.Oem = cjson.json_object_new_object()
    rsp.Oem["{{OemIdentifier}}"] = cjson.json_object_new_object()
    rsp.State = cjson.null

    local _, _, isDouble = string.match(sensor_obj.SensorIdentifier, '(FAN)(%d+)( [FR] )')
    local presence
    -- 当传感器禁止扫描时SensorReading拿到是'na'
    local reading = tonumber(sensor_obj.SensorReading)
    if isDouble == ' F ' then
        presence = fan_obj.FrontPresence
    elseif isDouble == ' R ' then
        presence = fan_obj.RearPresence
    else
        presence = fan_obj.FrontPresence
    end

    if presence == 0 then
        rsp.State = 'Absent'
        rsp.Health = cjson.null
        rsp.Oem["{{OemIdentifier}}"].Severity = cjson.null
    -- 传感器使能且能读到值
    elseif sensor_obj.Status == 'Enabled' and reading ~= 0 then
        rsp.State = 'Enabled'
        rsp.Health = health_t[sensor_obj.Health] or cjson.null
        rsp.Oem["{{OemIdentifier}}"].Severity = severity_t[sensor_obj.Health] or cjson.null
    -- 包括传感器禁止扫描或者不支持standby机型(传感器读数为0)
    else
        rsp.State = 'StandbyOffline'
        rsp.Health = health_t[sensor_obj.Health] or cjson.null
        rsp.Oem["{{OemIdentifier}}"].Severity = severity_t[sensor_obj.Health] or cjson.null
    end
    return rsp
end

local function get_speed_ratio(sensor_obj, thermal_fan_obj)
    local name = get_fan_name(sensor_obj.SensorIdentifier)
    if string.find(name, 'Front') then
        if thermal_fan_obj.FrontMaxSpeed > 0 then
            return thermal_fan_obj.FrontSpeed * thermal_fan_obj.Coefficient * 100 // thermal_fan_obj.FrontMaxSpeed
        else
            return 0
        end
    elseif string.find(name, 'Rear') then
        if thermal_fan_obj.RearMaxSpeed > 0 then
            return thermal_fan_obj.RearSpeed * thermal_fan_obj.Coefficient * 100 // thermal_fan_obj.RearMaxSpeed
        else
            return 0
        end
    end
end

local function get_fan_oem(sensor_obj, thermal_fan_obj)
    local rsp = cjson.json_object_new_object()
    rsp["{{OemIdentifier}}"] = cjson.json_object_new_object()
    rsp["{{OemIdentifier}}"].Position = thermal_fan_obj.Position
    rsp["{{OemIdentifier}}"].SpeedRatio = get_speed_ratio(sensor_obj, thermal_fan_obj)
    rsp["{{OemIdentifier}}"].SlotNumber = thermal_fan_obj.Slot
    rsp["{{OemIdentifier}}"].SlotId = thermal_fan_obj.Slot
    return rsp
end

local function get_threshold(value)
    -- 无告警阈值时候返回null
    if value == 'null' then
        return cjson.null
    end
    return tonumber(string.format('%.2f', value))
end

-- @function计算风扇传感器排序下标
-- 无法获取到thermal风扇以SensorNumber排序，属于异常场景无法保证顺序
-- 对称风扇F:slot*2-1 R:slot*2
-- 非对称风扇以slot做下标
local function get_fan_sort_index(fan_name, fan_obj, sensor_number)
    -- 当无法匹配到thermal风扇对象时，默认传入的是空的table
    if not fan_obj.Slot then
        return sensor_number
    end

    if string.find(fan_name, 'Front') then
        return fan_obj.Slot * 2 - 1
    end

    if string.find(fan_name, 'Rear') then
        return fan_obj.Slot * 2
    end

    return fan_obj.Slot
end

local function get_fan_reading_range(sensor_name, fan_obj)
    if not next(fan_obj) then
        return cjson.null
    end
    local name = get_fan_name(sensor_name)
    if string.find(name, 'Front') then
        return fan_obj.FrontMaxSpeed
    elseif string.find(name, 'Rear') then
        return fan_obj.RearMaxSpeed
    else
        return fan_obj.RearMaxSpeed
    end
end

local function get_fan(sensor_obj, thermal_fans)
    local rsp = cjson.json_object_new_object()
    -- 当无法拿到themal fan时，返回空table防止整个接口抛出异常
    local fan_obj = get_fan_obj(sensor_obj.SensorIdentifier, thermal_fans) or {}
    rsp['@odata.id'] = cjson.null -- 先占据位置初始化为null，排序中再赋值
    rsp['MemberId'] = cjson.null  -- 先占据位置初始化为null，排序中再赋值
    local fan_name = get_fan_name(sensor_obj.SensorIdentifier) -- 从风扇转速传感器的"SensorIdentifier"属性中获取redfish显示的风扇名称
    rsp['Name'] = fan_name
    rsp['Model'] = fan_obj.Model or cjson.null -- 当无法索引到thermal风扇时显示null
    rsp['Reading'] = get_fan_reading(sensor_obj.SensorIdentifier, fan_obj)
    rsp['LowerThresholdNonCritical'] = get_threshold(sensor_obj.LowerThresholdNonCritical)
    rsp['LowerThresholdCritical'] = get_threshold(sensor_obj.LowerThresholdCritical)
    rsp['LowerThresholdFatal'] = get_threshold(sensor_obj.LowerThresholdFatal)
    rsp['UpperThresholdNonCritical'] = get_threshold(sensor_obj.UpperThresholdNonCritical)
    rsp['UpperThresholdCritical'] = get_threshold(sensor_obj.UpperThresholdCritical)
    rsp['UpperThresholdFatal'] = get_threshold(sensor_obj.UpperThresholdFatal)
    rsp['MinReadingRange'] = 0
    rsp['MaxReadingRange'] = get_fan_reading_range(sensor_obj.SensorIdentifier, fan_obj)
    rsp['Status'] = get_fan_status(sensor_obj, fan_obj)
    rsp['ReadingUnits'] = 'RPM'
    rsp['PartNumber'] = fan_obj.PartNumber or cjson.null
    rsp['Oem'] = get_fan_oem(sensor_obj, fan_obj)
    local fan_slot = get_fan_sort_index(fan_name, fan_obj, sensor_obj.SensorNumber)
    return fan_slot, rsp
end

local function get_temperature_sensor_status(obj)
    local res = cjson.json_object_new_object()
    res.State = obj.Status -- 直接使用传感器当前读值状态
    res.Oem = cjson.json_object_new_object()
    res.Oem["{{OemIdentifier}}"] = cjson.json_object_new_object()
    res.Oem["{{OemIdentifier}}"].Severity = severity_t[obj.Health] or cjson.null
    res.Health = health_t[obj.Health] or cjson.null
    return res
end

-- 获取温度传感器的ReadingCelsius返回值
local function get_temperature_sensor_reading(value)
    -- 传感器禁止扫描时候将返回na
    if value == 'na' then
        return cjson.null
    end
    return tonumber(string.format('%.2f', value))
end

local function get_temperatures(Uri, sensor_obj, temperature_sensor_num)
    local rsp = cjson.json_object_new_object()
    rsp['@odata.id'] = '/redfish/v1/Chassis/' .. Uri.chassisid .. '/Thermal#/Temperatures/' .. temperature_sensor_num
    rsp['MemberId'] = tostring(temperature_sensor_num)
    rsp['SensorNumber'] = sensor_obj.SensorNumber
    rsp['Name'] = sensor_obj.SensorName
    rsp['ReadingCelsius'] = get_temperature_sensor_reading(sensor_obj.SensorReading)
    rsp['LowerThresholdNonCritical'] = get_threshold(sensor_obj.LowerThresholdNonCritical)
    rsp['LowerThresholdCritical'] = get_threshold(sensor_obj.LowerThresholdCritical)
    rsp['LowerThresholdFatal'] = get_threshold(sensor_obj.LowerThresholdFatal)
    rsp['UpperThresholdNonCritical'] = get_threshold(sensor_obj.UpperThresholdNonCritical)
    rsp['UpperThresholdCritical'] = get_threshold(sensor_obj.UpperThresholdCritical)
    rsp['UpperThresholdFatal'] = get_threshold(sensor_obj.UpperThresholdFatal)
    rsp['MinReadingRangeTemp'] = cjson.null
    rsp['MaxReadingRangeTemp'] = cjson.null
    rsp['Status'] = get_temperature_sensor_status(sensor_obj)
    return rsp
end

local function get_thermal_fan_status(fan_obj, fan_rotor_pos)
    local rsp = cjson.json_object_new_object()
    rsp.Health = cjson.null
    rsp.Oem = cjson.json_object_new_object()
    rsp.Oem["{{OemIdentifier}}"] = cjson.json_object_new_object()
    rsp.State = cjson.null

    local presence = fan_rotor_pos == ' Front' and fan_obj.FrontPresence or fan_obj.RearPresence
    local reading = fan_rotor_pos == ' Front' and fan_obj.FrontSpeed or fan_obj.RearSpeed
    if presence == 0 then
        rsp.State = 'Absent'
        rsp.Health = cjson.null
        rsp.Oem["{{OemIdentifier}}"].Severity = cjson.null
    -- 转速值非0
    elseif reading ~= 0 then
        rsp.State = 'Enabled'
        rsp.Health = 'OK'
        rsp.Oem["{{OemIdentifier}}"].Severity = 'Informational'
    -- 在位且转速获取为0
    else
        rsp.State = 'StandbyOffline'
        rsp.Health = 'OK'
        rsp.Oem["{{OemIdentifier}}"].Severity = 'Informational'
    end
    return rsp
end

-- 获取单个风扇信息
local function get_fan_info(fan_object, fan_rotor_pos)
    local rsp = cjson.json_object_new_object()
    local presence = fan_rotor_pos == ' Front' and fan_object.FrontPresence or fan_object.RearPresence
    rsp['@odata.id'] = cjson.null -- 先占据位置初始化为null，排序中再赋值
    rsp['MemberId'] = cjson.null  -- 先占据位置初始化为null，排序中再赋值
    rsp['Name'] = 'Fan' .. fan_object.Slot .. fan_rotor_pos
    rsp['Model'] = fan_object.Model
    if presence == 0 then
        rsp['Reading'] = cjson.null
    else
        rsp['Reading'] = fan_rotor_pos == ' Front' and fan_object.FrontSpeed * fan_object.Coefficient or
            fan_object.RearSpeed * fan_object.Coefficient
    end
    -- 解决无风扇转速传感器或者传感器数据计算异常场景，无法拿到传感器相关信息，对外显示为null
    rsp['LowerThresholdNonCritical'] = cjson.null
    rsp['LowerThresholdCritical'] = cjson.null
    rsp['LowerThresholdFatal'] = cjson.null
    rsp['UpperThresholdNonCritical'] = cjson.null
    rsp['UpperThresholdCritical'] = cjson.null
    rsp['UpperThresholdFatal'] = cjson.null
    rsp['MinReadingRange'] = 0
    rsp['MaxReadingRange'] = fan_rotor_pos == ' Front' and fan_object.FrontMaxSpeed or fan_object.RearMaxSpeed
    rsp['Status'] = get_thermal_fan_status(fan_object, fan_rotor_pos)
    rsp['ReadingUnits'] = 'RPM'
    rsp['PartNumber'] = fan_object.PartNumber

    local fan_oem = cjson.json_object_new_object()
    fan_oem["{{OemIdentifier}}"] = cjson.json_object_new_object()
    fan_oem["{{OemIdentifier}}"].Position = fan_object.Position
    fan_oem["{{OemIdentifier}}"].SpeedRatio = fan_rotor_pos == ' Front' and
        fan_object.FrontSpeed * fan_object.Coefficient * 100 // fan_object.FrontMaxSpeed or
        fan_object.RearSpeed * fan_object.Coefficient * 100 // fan_object.RearMaxSpeed
    fan_oem["{{OemIdentifier}}"].SlotNumber = fan_object.Slot
    fan_oem["{{OemIdentifier}}"].SlotId = fan_object.Slot
    rsp['Oem'] = fan_oem
    local fan_slot = get_fan_sort_index(fan_rotor_pos, fan_object)
    return fan_slot, rsp
end

-- 获取所有风扇信息
local function get_thermal_fans_info(thermal_fans)
    local fans_info = cjson.json_object_new_array()
    local fan_slot, fan_info
    for _, fan_obj in pairs(thermal_fans) do
        -- 对称风扇
        if fan_obj.IsTwins then
            fan_slot, fan_info = get_fan_info(fan_obj, ' Front')
            fans_info[fan_slot] = fan_info
            fan_slot, fan_info = get_fan_info(fan_obj, ' Rear')
            fans_info[fan_slot] = fan_info
        else
            fan_slot, fan_info = get_fan_info(fan_obj, '')
            fans_info[fan_slot] = fan_info
        end
    end
    return fans_info
end

local function get_sorted_fans_info(Uri, fans_info)
    local fan_cjson_array = cjson.json_object_new_array()
    local fan_sensor_num = 0
    -- 按照插入顺序再对风扇传感器返回体进行排序，优先级为fanid -> 单转子 -> Front -> Rear
    for _, fan_cjson_obj in ipairs(fans_info) do
        -- 插入排序可能存在key值跳变场景，此处拿到的cjson_object可能为userdata类型数据
        if cjson.json_object_is_object(fan_cjson_obj) then
            fan_cjson_obj['@odata.id'] = '/redfish/v1/Chassis/' .. Uri.chassisid .. '/Thermal#/Fans/' .. fan_sensor_num
            fan_cjson_obj['MemberId'] = tostring(fan_sensor_num)
            fan_sensor_num = fan_sensor_num + 1
            fan_cjson_array[fan_sensor_num] = fan_cjson_obj
        end
    end
    return fan_cjson_array
end

function m.get_thermal_sensor(Uri, sensor_list, thermal_fan_list)
    local fan_id
    local fan_sensor_cjson_obj = cjson.json_object_new_object()
    local res = cjson.json_object_new_object()
    local res1 = cjson.json_object_new_array()
    local res2 = cjson.json_object_new_array()
    local temperature_sensor_num = 0
    local thermal_fans = get_thermal_fans(thermal_fan_list)
    for _, obj in pairs(sensor_list) do
        -- 温度传感器
        if obj.SensorType == SENSOR_TYPE_TEMPERATURE then
            res1[#res1 + 1] = get_temperatures(Uri, obj, temperature_sensor_num)
            temperature_sensor_num = temperature_sensor_num + 1
        -- 风扇传感器
        elseif obj.SensorType == SENSOR_TYPE_FAN then
            -- 按照thermal风扇slot进行插入排序
            fan_id, fan_sensor_cjson_obj = get_fan(obj, thermal_fans)
            res2[fan_id] = fan_sensor_cjson_obj
        end
    end

    res[1] = m.get_temperature_sensors_summary(res1)
    res[2] = cjson.json_object_new_array()
    res[3] = res1
    res[4] = get_sorted_fans_info(Uri, res2)
    -- 风扇数组信息计算为空，则重新从thermal fan中重新计算风扇数组信息
    if #res[4] == 0 then
        local fans_info = get_thermal_fans_info(thermal_fans)
        res[4] = get_sorted_fans_info(Uri, fans_info)
    end
    return res
end

function m.check_inlet_policy_path(path)
    if not path or #path == 0 then
        local args = 'Oem/{{OemIdentifier}}/FanSpeedCustom/InletTemperature'
        local err = custom_messages.PropertyModificationNotSupported(args)
        err.RelatedProperties = {'#/' .. args}
        error(err)
    end

    return path
end

function m.check_disk_policy_path(path)
    if not path or #path == 0 then
        local args = 'Oem/{{OemIdentifier}}/FanSpeedCustom/DiskTemperature'
        local err = custom_messages.PropertyModificationNotSupported(args)
        err.RelatedProperties = {'#/' .. args}
        error(err)
    end

    return path
end

-- 入风口温度查询排序，保证Time在前，InletTempCelsius在后
function m.get_ordered_inlet_history_temp(data_src)
    local data_dest = cjson.json_object_new_array()
    for _, data in pairs(data_src) do
        local ordered_json_data = cjson.json_object_new_object()
        ordered_json_data.Time = data.Time
        ordered_json_data.InletTempCelsius = data.InletTempCelsius
        data_dest[#data_dest + 1] = ordered_json_data
    end
    return data_dest
end

-- 设置MPC开关时若没有资源树路径应当报错
function m.mpc_is_supported()
    local ok, rsp = pcall(mdb_service.is_valid_path, bus, '/bmc/kepler/Systems/1/MPCConfig')
    if not ok or not rsp.Result then
        log:error('The MPC resource tree path does not exist, err(%s)', rsp.message)
        local args = 'Oem/{{OemIdentifier}}/ModelPredictiveControlEnabled'
        local err = custom_messages.PropertyModificationNotSupported(args)
        err.RelatedProperties = {'#/' .. args}
        error(err)
    end
    return true
end

local mode_with_time = {
    ["Automatic"] = false,
    ["Manual"] = true,
    ["Mixed"] = false
}

-- 设置风扇模式控制为自动或混合模式的时候，不能设置手动模式超时时间
function m.fan_mode_and_timeout_not_conflict(mode, timeout)
    if not mode_with_time[mode] and math.type(timeout) == 'integer' then
        local arg1 = 'Oem/{{OemIdentifier}}/FanSpeedAdjustmentMode'
        local arg2 = 'Oem/{{OemIdentifier}}/FanManualModeTimeoutSeconds'
        local err = custom_messages.AttributeValueConflict(arg1, mode, arg2)
        --所抛错误是与属性相关时，需要填充该字段
        err.RelatedProperties = {'#/' .. arg1, '#/' .. arg2}
        error(err)
    end
    return true
end

-- 设置泵控制模式为自动的时候，不能设置手动模式超时时间
function m.liquid_cooling_mode_and_timeout_not_conflict(mode, timeout)
    if mode == 'Automatic' and math.type(timeout) == 'integer' then
        local arg1 = 'Oem/{{OemIdentifier}}/LiquidSpeedAdjustmentMode'
        local arg2 = 'Automatic'
        local arg3 = 'Oem/{{OemIdentifier}}/LiquidManualModeTimeoutSeconds'
        local err = custom_messages.AttributeValueConflict(arg1, arg2, arg3)
        --所抛错误是与属性相关时，需要填充该字段
        err.RelatedProperties = {'#/' .. arg1, '#/' .. arg3}
        error(err)
    end
    return true
end

-- 不支持泵的机型不能设置泵的手动转速属性
function m.liquid_cooling_level_set_supported(pump_ctrl_supported)
    if not pump_ctrl_supported then
        local arg = 'Oem/{{OemIdentifier}}/LiquidCoolingUnitsLevel'
        local err = custom_messages.PropertyModificationNotSupported(arg)
        err.RelatedProperties = {'#/' .. arg}
        error(err)
    end
    return true
end

-- 不支持泵的机型不能设置泵的模式和超时时间
function m.liquid_cooling_mode_set_supported(pump_ctrl_supported, mode, timeout)
    local arg1 = 'Oem/{{OemIdentifier}}/LiquidSpeedAdjustmentMode'
    local arg2 = 'Oem/{{OemIdentifier}}/LiquidManualModeTimeoutSeconds'
    if not pump_ctrl_supported then
        local err
        if mode and timeout then -- 设置mode和timeout
            return false -- 不抛出错误，已经对mode和timeout抛出错误，返回false不执行rpc方法即可
        elseif mode and not timeout then -- 仅设置mode
            err = custom_messages.PropertyModificationNotSupported(arg1)
            err.RelatedProperties = {'#/' .. arg1}
            error(err)
        else -- 仅设置timeout则mode会默认手动
            err = custom_messages.PropertyModificationNotSupported(arg2)
            err.RelatedProperties = {'#/' .. arg2}
            error(err)
        end
    end
    return true
end

return m
